/**
 * Copyright (C) 2013 Bohan
 *
 * This file is part of the Java SACNR API.
 *
 * Java SACNR API is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Java SACNR API is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package bohan.sacnr.api;

import bohan.sacnr.exception.ItemNotFoundException;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.MalformedURLException;

/**
 * This class does not decode HTML entities, you will have to do it yourself
 * @author Bohan
 * @version 1.0
 */
public final class Player
{

    static final String EXCEPTION_MESSAGE = "sacnr.com's syntax has changed, "
            + "notify Bohan to update the API.";
    private final String playerName;
    private final String timePlayed, timeRegistered, timeLastLogin;
    private final int playerID, skinID, regularPlayerLevel;
    private final boolean isDonatingPlayer, isOnline, isBanned, isStaff;
    private final long updated;
    private GeneralStats general = null;
    private MoneyStats money = null;

    /**
     * Initializes and parses all informations on the the player's homepage:
     * http://www.sacnr.com/u/[playerName] *EXPENSIVE*.
     * <p>
     * Since this constructor connects to the sacnr website to parse player
     * stats, initializing a <code>Player</code> object is always expensive.
     * @param playerName Name of player
     * @throws IOException when the API is unable to connect to sacnr's server.
     * @throws RuntimeException A wrapped MalformedURLException. This exception
     * is caught by the API as it should not occur, but in case it does, it is
     * thrown as a RunTimeException
     * @throws IllegalArgumentException when the sacnr.com's syntax has changed,
     * please notify Bohan to update the API.
     */
    public Player(String playerName) throws IOException
    {
        String name = playerName.toLowerCase();
        String data;
        try
        {
            URL page = new URL("http://sacnr.com/u/" + name);
            BufferedReader in = new BufferedReader(
                    new InputStreamReader(page.openStream()));
            StringBuilder sb = new StringBuilder(5000);
            while ((data = in.readLine()) != null)
                sb.append(data);
            in.close();
            updated = System.currentTimeMillis();
            data = sb.toString();
        } catch (MalformedURLException e)
        {
            /**
             * This exception should not occur, but in case it does, re-wrap it
             * and throw as a RunTimeException
             */
            throw new RuntimeException("MalformedURLException: " + e.getMessage(), e);
        }

        if (data.indexOf("<h2>Welcome to SACNR.com!</h2>") >= 0)
            throw new ItemNotFoundException("The player " + playerName + " was not found.");

        int index;
        if ((index = data.indexOf("<h2>") + 4) < 4)
            throw new IllegalArgumentException(EXCEPTION_MESSAGE);
        this.playerName = data.substring(index, data.indexOf('<', index));

        if ((index = data.indexOf("var user_id=", index) + 12) >= 12)
            try
            {
                playerID = Integer.parseInt(data.substring(index, data.indexOf(';', index)));
            } catch (Exception e)
            {
                throw new IllegalArgumentException(EXCEPTION_MESSAGE);
            }
        else
            throw new IllegalArgumentException(EXCEPTION_MESSAGE);

        if ((index = data.indexOf("http://www.sacnr.com/img/skins/Skin_", index) + 36) >= 36)
            try
            {
                skinID = Integer.parseInt(data.substring(index, data.indexOf('.', index)));
            } catch (Exception e)
            {
                throw new IllegalArgumentException(EXCEPTION_MESSAGE);
            }
        else
            throw new IllegalArgumentException(EXCEPTION_MESSAGE);

        if ((index = data.indexOf("Played for <b>", index) + 14) >= 14)
            timePlayed = data.substring(index, data.indexOf('<', index));
        else
            throw new IllegalArgumentException(EXCEPTION_MESSAGE);

        if ((index = data.indexOf("Registered <b>", index) + 14) >= 14)
            timeRegistered = data.substring(index, data.indexOf('<', index));
        else
            throw new IllegalArgumentException(EXCEPTION_MESSAGE);

        if ((index = data.indexOf("Last logged in <b>", index) + 18) >= 18)
            timeLastLogin = data.substring(index, data.indexOf('<', index));
        else
            throw new IllegalArgumentException(EXCEPTION_MESSAGE);

        isOnline = (data.indexOf("Online now", index) >= 0) ? true : false;

        isBanned = (data.indexOf("Banned", index) >= 0) ? true : false;

        isStaff = (data.indexOf("Staff member", index) >= 0) ? true : false;

        isDonatingPlayer = (data.indexOf("Donating player", index) >= 0) ? true : false;

        if ((index = data.indexOf("Regular player level ", index) + 21) >= 21)
            if (Character.isDigit(data.charAt(index + 1)))
                regularPlayerLevel = Integer.parseInt(data.substring(index, index + 2));
            else
                regularPlayerLevel = Integer.parseInt(data.substring(index, index + 1));
        else
            regularPlayerLevel = 0; //Normal Player
    }

    /**
     * 
     * @return The time since this player's data was last updated in 
     * milliseconds
     */
    public long getAge()
    {
        return System.currentTimeMillis() - updated;
    }

    public int getPlayerID()
    {
        return this.playerID;
    }

    public int getSkinID()
    {
        return this.skinID;
    }

    /**
     * 
     * @return The player's name in correct capitalization
     */
    public String getPlayerName()
    {
        return this.playerName;
    }

    public String getTimePlayed()
    {
        return this.timePlayed;
    }

    public String getTimeRegistered()
    {
        return this.timeRegistered;
    }

    public String getTimeLastLogin()
    {
        return this.timeLastLogin;
    }

    /**
     * Gets the player's regular player level.
     * @return The player's regular player level, 0 if the player is normal 
     * player.
     */
    public int getRegularPlayerLevel()
    {
        return this.regularPlayerLevel;
    }

    public boolean isStaff()
    {
        return this.isStaff;
    }

    public boolean isDonatingPlayer()
    {
        return this.isDonatingPlayer;
    }

    public boolean isOnline()
    {
        return this.isOnline;
    }

    public boolean isBanned()
    {
        return this.isBanned;
    }

    /**
     * Gets the player's <code>GeneralStats</code> object that represents
     * this player's general stats.
     * <p>
     * Expensive on the first call, subsequent calls are cheap for each
     * instance of <code>Player</code>
     * @return The <code>GeneralStats</code> object for this player.
     * @throws IOException Thrown from <code>GeneralStats</code> constructor 
     * when it can't connect to the server
     * @see GeneralStats
     */
    public GeneralStats getGeneralStats() throws IOException
    {
        if (general == null)
            general = new GeneralStats(playerID);
        return general;
    }
    
    /**
     * Gets the player's <code>MoneyStats</code> object that represents
     * this player's money stats.
     * <p>
     * Expensive on the first call, subsequent calls are cheap for each
     * instance of <code>Player</code>
     * @return The <code>MoneyStats</code> object for this player.
     * @throws IOException Thrown from <code>MoneyStats</code> constructor 
     * when it can't connect to the server
     * @see MoneyStats
     */
    public MoneyStats getMoneyStats() throws IOException
    {
        if (money == null)
            money = new MoneyStats(playerID);
        return money;
    }
    
    /**
     * Creates a new instance of <code>Player</code> object with updated player
     * stats *EXPENSIVE*.
     * <p>
     * Note that since <code>Player</code> is an immutable class, this
     * method does not update the current <code>Player</code> instance, but
     * returns a new updated instance of a <code>Player</code> object. 
     * <p>
     * Note that this method is always expensive, as it connects to the sacnr 
     * website to update the player's stats.
     * @return An up to date <code>Player</code> object.
     * @throws IOException Thrown from the <code>Player</code> constructor when
     * it can't connect to the server.
     * @see Player#Player(String) 
     */
    public Player update() throws IOException
    {
        return new Player(playerName);
    }
}